package com.bolt.support.spring.jpa.specification;

import com.bolt.common.collection.IteratorUtil;
import com.bolt.common.reflect.AnnotationUtil;
import com.bolt.common.reflect.ClassUtil;
import com.bolt.common.reflect.ObjectUtil;
import com.bolt.common.utils.ArrayUtil;
import com.bolt.common.utils.SpringContextUtil;
import com.bolt.common.utils.StrUtil;
import com.bolt.support.query.conditions.domain.DataScopeAware;
import com.bolt.support.query.conditions.domain.MatchType;
import com.bolt.support.spring.jpa.annotation.DataAccess;
import com.bolt.support.spring.jpa.annotation.Filter;
import com.bolt.support.spring.jpa.jpql.Clauses;
import com.bolt.support.spring.jpa.jpql.ColumnClause;
import com.bolt.support.spring.jpa.jpql.CompareClause;
import com.bolt.support.spring.jpa.jpql.Ops;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.query.criteria.internal.expression.LiteralExpression;
import org.joda.time.DateTime;
import org.springframework.util.Assert;

import javax.persistence.criteria.*;
import java.lang.reflect.Field;
import java.util.*;

/**
 * Created by Administrator on 2020/9/25.
 */
@Slf4j
public class QueryHelp {

    public static <Q> CompareClause getOperationClause(Q query) {
        CompareClause compareClause = new CompareClause();
        if (query == null) {
            return compareClause;
        }
        DataAccess permission = query.getClass().getAnnotation(DataAccess.class);
        if (permission != null) {
            DataScopeAware dataScopeAware;
            if (StrUtil.isBlank(permission.dataScopeAwareRef())) {
                dataScopeAware = SpringContextUtil.getBean(DataScopeAware.class);
            } else {
                dataScopeAware = SpringContextUtil.getBean(permission.dataScopeAwareRef(), DataScopeAware.class);
            }
            if (!dataScopeAware.isSuperUser()) {
                Set<?> dataScopes = dataScopeAware.getCurrentDataScope();
                if (StrUtil.isNotBlank(permission.fieldName()) && StrUtil.isBlank(permission.joinName()) && IteratorUtil.isNotEmpty(dataScopes)) {
                    ColumnClause property = new ColumnClause(permission.fieldName(), query.getClass());
                    CompareClause dataScopesClause = new CompareClause(permission.matchType(), property, dataScopes);
                    compareClause.and(dataScopesClause);
                } else if (StrUtil.isNotBlank(permission.fieldName()) && StrUtil.isNotBlank(permission.joinName()) && IteratorUtil.isNotEmpty(dataScopes)) {

                }
            }
        }

        try {
            List<Field> fields = ClassUtil.getAllFields(query.getClass());
            for (Field field : fields) {
                Filter filter = AnnotationUtil.getAnnotation(field, Filter.class);
                if (ObjectUtil.isNotNull(filter)) {
                    Class type = filter.owned();
                    String blurry = filter.blurry();
                    String propName = StrUtil.isBlank(filter.name()) ? field.getName() : filter.name();
                    Object matchValue = field.get(query);
                    if (ObjectUtil.isEmpty(matchValue) || "".equals(matchValue) || type.isAssignableFrom(void.class)) {
                        continue;
                    }
                    if (StrUtil.isNotEmpty(blurry)) {
                        String[] blurryWords = blurry.split(",");
                        for (String s : blurryWords) {
                            compareClause.or((buildOperationClause(MatchType.BW, matchValue, type, s, s)).render(false));
                        }
                        continue;
                    }
                    CompareClause compare = buildOperationClause(filter.type(), matchValue, type, propName, propName);
                    compareClause.and(compare.render(false));
                }
            }

        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }
        return compareClause;
    }

    public static <R, Q> Predicate getPredicate(Root<R> root, Q query, CriteriaBuilder cb) {
        List<Predicate> predicates = new ArrayList<>();
        if (query == null) {
            return cb.and(predicates.toArray(new Predicate[0]));
        }
        // 数据权限
        DataAccess permission = query.getClass().getAnnotation(DataAccess.class);
        if (permission != null) {
            DataScopeAware dataScopeAware;
            if (StrUtil.isBlank(permission.dataScopeAwareRef())) {
                dataScopeAware = SpringContextUtil.getBean(DataScopeAware.class);
            } else {
                dataScopeAware = SpringContextUtil.getBean(permission.dataScopeAwareRef(), DataScopeAware.class);
            }
            if (!dataScopeAware.isSuperUser()) {
                Set<?> dataScopes = dataScopeAware.getCurrentDataScope();
                if (IteratorUtil.isNotEmpty(dataScopes)) {
                    if (StrUtil.isNotBlank(permission.joinName()) && StrUtil.isNotBlank(permission.fieldName())) {
                        Join join = root.join(permission.joinName(), JoinType.LEFT);
                        predicates.add(getExpression(permission.fieldName(), join, root).in(dataScopes));
                    } else if (StrUtil.isBlank(permission.joinName()) && StrUtil.isNotBlank(permission.fieldName())) {
                        predicates.add(getExpression(permission.fieldName(), null, root).in(dataScopes));
                    }
                }
            }
        }
        try {
            List<Field> fields = ClassUtil.getAllFields(query.getClass());
            for (Field field : fields) {
                Filter filter = AnnotationUtil.getAnnotation(field, Filter.class);
                if (ObjectUtil.isNotNull(filter)) {
                    String joinName = filter.joinName();
                    String blurry = filter.blurry();
                    String propName = StrUtil.isBlank(filter.name()) ? field.getName() : filter.name();
                    Object matchValue = field.get(query);
                    if (ObjectUtil.isEmpty(matchValue)) {
                        continue;
                    }
                    if (StrUtil.isNotEmpty(blurry)) {
                        String[] blurrys = blurry.split(",");
                        List<Predicate> orPredicate = new ArrayList<>();
                        for (String s : blurrys) {
                            orPredicate.add(cb.like(root.get(s)
                                    .as(String.class), "%" + matchValue.toString() + "%"));
                        }
                        Predicate[] p = new Predicate[orPredicate.size()];
                        predicates.add(cb.or(orPredicate.toArray(p)));
                        continue;
                    }
                    Join join = null;
                    if (StrUtil.isNotEmpty(joinName)) {
                        String[] joinNames = joinName.split(",");
                        for (String name : joinNames) {
                            switch (filter.join()) {
                                case LEFTJOIN:
                                    if (ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(matchValue)) {
                                        join = join.join(name, JoinType.LEFT);
                                    } else {
                                        join = root.join(name, JoinType.LEFT);
                                    }
                                    break;
                                case RIGHTJOIN:
                                    if (ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(matchValue)) {
                                        join = join.join(name, JoinType.RIGHT);
                                    } else {
                                        join = root.join(name, JoinType.RIGHT);
                                    }
                                    break;
                                default:
                                    if (ObjectUtil.isNotNull(join) && ObjectUtil.isNotNull(matchValue)) {
                                        join = join.join(name, JoinType.INNER);
                                    } else {
                                        join = root.join(name, JoinType.INNER);
                                    }
                                    break;
                            }
                        }
                    }

                    Expression expression = getExpression(propName, join, root);
                    Predicate predicate = buildPredicate(filter.type(), matchValue, cb, expression);
                    if (predicate != null) {
                        predicates.add(predicate);
                    }
                }
            }

        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }
        int size = predicates.size();
        return cb.and(predicates.toArray(new Predicate[size]));
    }


    public static <T, R> Expression<T> getExpression(String attributeName, Join join, Root<R> root) {
        if (ObjectUtil.isNotNull(join)) {
            return join.get(attributeName);
        } else {
            return root.get(attributeName);
        }
    }

    /**
     * 用于兼容旧有版本
     *
     * @param ops
     * @return
     */
    public static MatchType toMatchType(Ops ops) {
        MatchType matchType = MatchType.EQ;
        switch (ops) {
            case EQ:
                matchType = MatchType.EQ;
                break;
            case NE:
                matchType = MatchType.NE;
                break;
            case ISNULL:
                matchType = MatchType.NU;
                break;
            case NOTNULL:
                matchType = MatchType.NN;
                break;
            case LIKE:
                matchType = MatchType.CN;
                break;
            case LIKERIGHT:
                matchType = MatchType.BW;
                break;
            case LIKELEFT:
                matchType = MatchType.EW;
                break;
            case BETWEEN:
                matchType = MatchType.BT;
                break;
            case GT:
                matchType = MatchType.GT;
                break;
            case GE:
                matchType = MatchType.GE;
                break;
            case LE:
                matchType = MatchType.LE;
                break;
            case LT:
                matchType = MatchType.LT;
                break;
            case IN:
                matchType = MatchType.IN;
                break;
            case NOTIN:
                matchType = MatchType.NI;
                break;
            case CONTAINS:
                matchType = MatchType.CONTAINS;
                break;
            default:
                break;

        }
        return matchType;
    }

    public static CompareClause buildOperationClause(MatchType matchType, Object matchValue,
                                                     Class type, String propName, String alias) {
        CompareClause compareClause = new CompareClause();
        ColumnClause property = new ColumnClause(propName, type);
        switch (matchType) {
            case EQ:
                // 对日期特殊处理：一般用于区间日期的结束时间查询,如查询2012-01-01之前,一般需要显示2010-01-01当天及以前的数据,
                // 而数据库一般存有时分秒,因此需要特殊处理把当前日期+1天,转换为<2012-01-02进行查询
                if (matchValue instanceof Date) {
                    DateTime dateTime = new DateTime(((Date) matchValue).getTime());
                    if (dateTime.getHourOfDay() == 0 && dateTime.getMinuteOfHour() == 0
                            && dateTime.getSecondOfMinute() == 0) {
                        compareClause.between(property, dateTime.toDate(), dateTime.plusDays(1).toDate());
                    }
                }
                compareClause.eq(property, Clauses.of(alias, matchValue));
                break;
            case NE:
                compareClause.ne(property, Clauses.of(alias, matchValue));
                break;
            case BK:
                compareClause.isBlank(new ColumnClause(propName, type));
                break;
            case NB:
                compareClause.notBlank(new ColumnClause(propName, type));
                break;
            case NU:
                if (matchValue instanceof Boolean && (Boolean) matchValue == false) {
                    compareClause.notNull(new ColumnClause(propName, type));
                } else {
                    compareClause.isNull(new ColumnClause(propName, type));

                }
                break;
            case NN:
                if (matchValue instanceof Boolean && (Boolean) matchValue == false) {
                    compareClause.isNull(new ColumnClause(propName, type));
                } else {
                    compareClause.notNull(new ColumnClause(propName, type));
                }
                break;
            case CN:
                compareClause.like(new ColumnClause(propName, type), (String) matchValue);
                break;
            case NC:
                compareClause.notLike(new ColumnClause(propName, type), (String) matchValue);
                break;
            case BW:
                compareClause.likeRight(new ColumnClause(propName, type), (String) matchValue);
                break;
            case EW:
                compareClause.likeLeft(new ColumnClause(propName, type), (String) matchValue);
                break;

            case BT:
                Object[] matchValues;
                if (matchValue.getClass().isArray()) {
                    matchValues = (Object[]) matchValue;
                } else if (matchValue instanceof Collection) {
                    matchValues = ((Collection) matchValue).toArray();
                } else {
                    matchValues = new Object[]{matchValue};
                }
                // 对日期特殊处理：一般用于区间日期的结束时间查询,如查询2012-01-01之前,一般需要显示2010-01-01当天及以前的数据,
                // 而数据库一般存有时分秒,因此需要特殊处理把当前日期+1天,转换为<2012-01-02进行查询
                if (matchValues[0] instanceof Date && matchValues.length == 1) {
                    DateTime dateFrom = new DateTime(((Date) matchValues[0]).getTime());
                    compareClause.between(new ColumnClause(propName, type), dateFrom.toDate(), dateFrom.plusDays(1).toDate());
                } else {
                    if (matchValues.length > 1) {
                        compareClause.between(new ColumnClause(propName, type), matchValues[0], matchValues[1]);
                    }
                }
                break;
            case GT:
                compareClause.gt(property, Clauses.of(alias, matchValue));
                break;
            case GE:
                compareClause.ge(property, Clauses.of(alias, matchValue));
                break;
            case LT:
                compareClause.lt(property, Clauses.of(alias, matchValue));
                break;
            case LE:
                compareClause.le(property, Clauses.of(alias, matchValue));
                break;
            case IN:
                if (matchValue.getClass().isArray()) {
                    compareClause.in(new ColumnClause(propName, type), Arrays.asList(matchValue));
                } else if (matchValue instanceof Collection) {
                    compareClause.in(new ColumnClause(propName, type), (Collection) matchValue);
                } else {
                    compareClause.eq(property, Clauses.of(property, matchValue));
                }
                break;
            case NI:
                if (matchValue.getClass().isArray()) {
                    compareClause.notIn(new ColumnClause(propName, type), Arrays.asList(matchValue));
                } else if (matchValue instanceof Collection) {
                    compareClause.notIn(new ColumnClause(propName, type), (Collection) matchValue);
                } else {
                    compareClause.ne(property, Clauses.of(property, matchValue));
                }
                break;
            case CONTAINS:
                compareClause.contains(property, (String) matchValue);
                break;
            default:
                break;
        }
        return compareClause;
    }

    public static Predicate buildPredicate(MatchType type, Object matchValue, CriteriaBuilder builder, Expression expression) {
        Predicate predicate = null;
        switch (type) {
            case EQ:
                // 对日期特殊处理：一般用于区间日期的结束时间查询,如查询2012-01-01之前,一般需要显示2010-01-01当天及以前的数据,
                // 而数据库一般存有时分秒,因此需要特殊处理把当前日期+1天,转换为<2012-01-02进行查询
                if (matchValue instanceof Date) {
                    DateTime dateTime = new DateTime(((Date) matchValue).getTime());
                    if (dateTime.getHourOfDay() == 0 && dateTime.getMinuteOfHour() == 0
                            && dateTime.getSecondOfMinute() == 0) {
                        return builder.and(builder.greaterThanOrEqualTo(expression, dateTime.toDate()),
                                builder.lessThan(expression, dateTime.plusDays(1).toDate()));
                    }
                }

                predicate = builder.equal(expression, matchValue);
                break;
            case NE:
                // 对日期特殊处理：一般用于区间日期的结束时间查询,如查询2012-01-01之前,一般需要显示2010-01-01当天及以前的数据,
                // 而数据库一般存有时分秒,因此需要特殊处理把当前日期+1天,转换为<2012-01-02进行查询
                if (matchValue instanceof Date) {
                    DateTime dateTime = new DateTime(((Date) matchValue).getTime());
                    if (dateTime.getHourOfDay() == 0 && dateTime.getMinuteOfHour() == 0
                            && dateTime.getSecondOfMinute() == 0) {
                        return builder.or(builder.lessThan(expression, dateTime.toDate()),
                                builder.greaterThanOrEqualTo(expression, dateTime.plusDays(1).toDate()));
                    }
                }
                predicate = builder.notEqual(expression, matchValue);
                break;
            case BK:
                predicate = builder.or(builder.isNull(expression), builder.equal(expression, ""));
                break;
            case NB:
                predicate = builder.and(builder.isNotNull(expression), builder.notEqual(expression, ""));
                break;
            case NU:
                if (matchValue instanceof Boolean && (Boolean) matchValue == false) {
                    predicate = builder.isNotNull(expression);
                } else {
                    predicate = builder.isNull(expression);
                }
                break;
            case NN:
                if (matchValue instanceof Boolean && (Boolean) matchValue == false) {
                    predicate = builder.isNull(expression);
                } else {
                    predicate = builder.isNotNull(expression);
                }
                break;
            case CN:
                predicate = builder.like(expression, "%" + matchValue + "%");
                break;
            case NC:
                predicate = builder.notLike(expression, "%" + matchValue + "%");
                break;
            case BW:
                predicate = builder.like(expression, matchValue + "%");
                break;
            case BN:
                predicate = builder.notLike(expression, matchValue + "%");
                break;
            case EW:
                predicate = builder.like(expression, "%" + matchValue);
                break;
            case EN:
                predicate = builder.notLike(expression, "%" + matchValue);
                break;
            case BT:
                Object[] matchValues;
                if (matchValue.getClass().isArray()) {
                    matchValues = (Object[]) matchValue;
                } else if (matchValue instanceof Collection) {
                    matchValues = ((Collection) matchValue).toArray();
                } else {
                    matchValues = new Object[]{matchValue};
                }
                Assert.isTrue(matchValues.length == 2, "Match value must have two value");
                // 对日期特殊处理：一般用于区间日期的结束时间查询,如查询2012-01-01之前,一般需要显示2010-01-01当天及以前的数据,
                // 而数据库一般存有时分秒,因此需要特殊处理把当前日期+1天,转换为<2012-01-02进行查询
                if (matchValues[0] instanceof Date && matchValues.length == 1) {
                    DateTime dateFrom = new DateTime(((Date) matchValues[0]).getTime());
                    predicate = builder.and(builder.greaterThanOrEqualTo(expression, dateFrom.toDate()),
                            builder.lessThan(expression, dateFrom.plusDays(1).toDate()));
                } else {
                    if (matchValues.length > 1) {
                        predicate = builder.between(expression, (Comparable) matchValues[0], (Comparable) matchValues[1]);
                    }
                }
                break;
            case GT:
                predicate = builder.greaterThan(expression, (Comparable) matchValue);
                break;
            case GE:
                predicate = builder.greaterThanOrEqualTo(expression, (Comparable) matchValue);
                break;
            case LT:
                // 对日期特殊处理：一般用于区间日期的结束时间查询,如查询2012-01-01之前,一般需要显示2010-01-01当天及以前的数据,
                // 而数据库一般存有时分秒,因此需要特殊处理把当前日期+1天,转换为<2012-01-02进行查询
                if (matchValue instanceof Date) {
                    DateTime dateTime = new DateTime(((Date) matchValue).getTime());
                    if (dateTime.getHourOfDay() == 0 && dateTime.getMinuteOfHour() == 0
                            && dateTime.getSecondOfMinute() == 0) {
                        return builder.lessThan(expression, dateTime.plusDays(1).toDate());
                    }
                }
                predicate = builder.lessThan(expression, (Comparable) matchValue);
                break;
            case LE:
                // 对日期特殊处理：一般用于区间日期的结束时间查询,如查询2012-01-01之前,一般需要显示2010-01-01当天及以前的数据,
                // 而数据库一般存有时分秒,因此需要特殊处理把当前日期+1天,转换为<2012-01-02进行查询
                if (matchValue instanceof Date) {
                    DateTime dateTime = new DateTime(((Date) matchValue).getTime());
                    if (dateTime.getHourOfDay() == 0 && dateTime.getMinuteOfHour() == 0
                            && dateTime.getSecondOfMinute() == 0) {
                        return builder.lessThan(expression, dateTime.plusDays(1).toDate());
                    }
                }
                predicate = builder.lessThanOrEqualTo(expression, (Comparable) matchValue);
                break;
            case IN:
                if (matchValue.getClass().isArray() && ArrayUtil.isNotEmpty(matchValue)) {
                    predicate = expression.in((Object[]) matchValue);
                } else if (matchValue instanceof Collection && IteratorUtil.isNotEmpty((Collection) matchValue)) {
                    predicate = expression.in((Collection) matchValue);
                } else {
                    break;
                }
                break;
            case NI:
                if (matchValue.getClass().isArray() && ArrayUtil.isNotEmpty(matchValue)) {
                    predicate = builder.not(expression.in((Object[]) matchValue));
                } else if (matchValue instanceof Collection && IteratorUtil.isNotEmpty((Collection) matchValue)) {
                    predicate = builder.not(expression.in((Collection) matchValue));
                } else {
                    break;
                }
                break;
            case CONTAINS:
                LiteralExpression literalExpression = new LiteralExpression(null, matchValue);
                Expression<Integer> expressionInstr = builder.function("instr", Integer.class, expression, literalExpression);
                predicate = builder.gt(expressionInstr, 0);
                break;
            case ACLPREFIXS:
                List<Predicate> aclPredicates = new ArrayList();
                Collection<String> aclCodePrefixs = (Collection<String>) matchValue;
                for (String aclCodePrefix : aclCodePrefixs) {
                    if (StrUtil.isNotBlank(aclCodePrefix)) {
                        aclPredicates.add(builder.like(expression, aclCodePrefix + "%"));
                    }

                }
                if (aclPredicates.size() == 0) {
                    return null;
                }
                predicate = builder.or(aclPredicates.toArray(new Predicate[aclPredicates.size()]));
                break;
            default:
                break;
        }
        return predicate;
    }


}

 